#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <termios.h>
#include <pthread.h>
#include "slist.h"

#define TUN_DEVICE "/dev/net/tun"
#define BUFFER_SIZE 1500
#define SERIAL_PORT "/dev/ttyS8"

struct data_packet_s {
    uint8_t *data_buf;
    uint16_t data_len;
    slist_t list;
};

struct data_info_s {
    int tun_fd, serial_fd;
    pthread_t tun_tid;
    pthread_t ser_tx_tid, ser_rx_tid;
    slist_t ser_to_tun_list;
    slist_t tun_to_ser_list;
    pthread_mutex_t lock;
};

static struct data_info_s data_info;

static void data_info_lock(void)
{
    pthread_mutex_lock(&data_info.lock);
}

static void data_info_unlock(void)
{
    pthread_mutex_unlock(&data_info.lock);
}

int tun_alloc(char *dev) {
    struct ifreq ifr;
    int fd, err;

    if ((fd = open(TUN_DEVICE, O_RDWR)) < 0) {
        perror("Opening TUN device");
        return fd;
    }

    memset(&ifr, 0, sizeof(ifr));
    ifr.ifr_flags = IFF_TUN | IFF_NO_PI;

    if (*dev) {
        strncpy(ifr.ifr_name, dev, IFNAMSIZ);
    }

    if ((err = ioctl(fd, TUNSETIFF, (void *)&ifr)) < 0) {
        perror("ioctl(TUNSETIFF)");
        close(fd);
        return err;
    }

    strcpy(dev, ifr.ifr_name);
    return fd;
}

int open_serial(const char *device) {
    int fd = open(device, O_RDWR | O_NOCTTY);
    if (fd < 0) {
        perror("Opening serial port");
        return fd;
    }

    struct termios options;
    tcgetattr(fd, &options);
    cfsetispeed(&options, B921600);
    cfsetospeed(&options, B921600);
    options.c_cflag &= ~CSIZE;
    options.c_cflag |= CS8;
    options.c_cflag &= ~PARENB;                             /*清奇偶校验位*/
    options.c_iflag &= ~INPCK;                              /*奇偶校验无效*/
    options.c_cflag &= ~CSTOPB;                             /*设置停止位为1*/
    tcflush(fd, TCIFLUSH);                                  /* 清输入输出队列 */
    options.c_cc[VTIME] = 100;                              /* 设置超时15 seconds*/
    options.c_cc[VMIN] = 0;                                 /* 更新设置，使马上生效 */
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);     /* 输入 */
    options.c_oflag &= ~OPOST;                              /* 输出 */
    options.c_iflag &= ~IGNCR;                              /*不忽略收到的回车字符*/
    options.c_iflag &= ~ICRNL;                              /*不转换*/
    options.c_iflag &= ~(IXON | IXOFF | IXANY);             /*关硬件流控*/
    tcsetattr(fd, TCSANOW, &options);

    return fd;
}

static void *ser_tx_thread(void *ptr)
{
    struct data_packet_s *data_packet;
    while (1)
    {
        if (slist_isempty(&data_info.tun_to_ser_list) == 0)
        {
            usleep(1000);
            continue;
        }
        data_info_lock();
        data_packet = slist_first_entry(&data_info.tun_to_ser_list, struct data_packet_s, list);
        data_info_unlock();
        printf("Received %d bytes from TUN device\n", data_packet->data_len);
        write(data_info.serial_fd, data_packet->data_buf, data_packet->data_len);
        tcdrain(data_info.serial_fd);
        free(data_packet);
    }
    exit(EXIT_FAILURE);
}

static void *ser_rx_thread(void *ptr)
{
    char buffer[BUFFER_SIZE];
    int data_len, recv_len, ret;
    fd_set read_fds;
    struct timeval timeout;
    struct data_packet_s *data_packet;
    while (1)
    {
        FD_ZERO(&read_fds);
        FD_SET(data_info.serial_fd, &read_fds);

        timeout.tv_sec = 0;
        timeout.tv_usec = 500;

        ret = select(data_info.serial_fd + 1, &read_fds, NULL, NULL, &timeout);
        if (ret < 0) {
            perror("select");
            break;
        }
        if (FD_ISSET(data_info.serial_fd, &read_fds))
        {
            data_len = 0;
            do {
                FD_ZERO(&read_fds);
                FD_SET(data_info.serial_fd, &read_fds);

                timeout.tv_sec = 0;
                timeout.tv_usec = 500;
                if (ret < 0) {
                    perror("select");
                    data_len = -1;
                    break;
                }
                else if (ret == 0)
                {
                    break;
                }
                recv_len = read(data_info.serial_fd, &buffer[data_len], BUFFER_SIZE - data_len);
                if (recv_len > 0)
                {
                    data_len += recv_len;
                }
            } while (data_len > 0 && data_len < BUFFER_SIZE);
            if (data_len < 0) {
                perror("Reading from serial port");
                break;
            }
            else if (data_len > 0)
            {
                printf("Received %d bytes from serial port\n", data_len);
                data_packet = (struct data_packet_s *)malloc(sizeof(struct data_packet_s) + data_len);
                if (data_packet == NULL)
                {
                    perror("malloc");
                    break;
                }
                memset(data_packet, 0, sizeof(struct data_packet_s) + data_len);
                data_packet->data_buf = (uint8_t *)&data_packet[1];
                memcpy(data_packet->data_buf, buffer, data_len);
                data_packet->data_len = data_len;
                data_info_lock();
                slist_append(&data_info.ser_to_tun_list, &data_packet->list);
                data_info_unlock();
            }
        }
    }
    exit(EXIT_FAILURE);
}

static void *tun_thread(void *ptr)
{
    char buffer[BUFFER_SIZE];
    int data_len, ret;
    fd_set read_fds;
    struct timeval timeout;
    struct data_packet_s *data_packet;
    while (1)
    {
        FD_ZERO(&read_fds);
        FD_SET(data_info.tun_fd, &read_fds);

        timeout.tv_sec = 0;
        timeout.tv_usec = 500;

        ret = select(data_info.tun_fd + 1, &read_fds, NULL, NULL, &timeout);
        if (ret < 0) {
            perror("select");
            break;
        }
        if (FD_ISSET(data_info.tun_fd, &read_fds)) {
            data_len = read(data_info.tun_fd, buffer, BUFFER_SIZE);
            if (data_len < 0) {
                perror("Reading from TUN device");
                break;
            }
            else if (data_len > 0)
            {
                data_packet = (struct data_packet_s *)malloc(sizeof(struct data_packet_s) + data_len);
                if (data_packet == NULL)
                {
                    perror("malloc");
                    break;
                }
                memset(data_packet, 0, sizeof(struct data_packet_s) + data_len);
                data_packet->data_buf = (uint8_t *)&data_packet[1];
                memcpy(data_packet->data_buf, buffer, data_len);
                data_packet->data_len = data_len;
                data_info_lock();
                slist_append(&data_info.tun_to_ser_list, &data_packet->list);
                data_info_unlock();
            }
        }

        while (!slist_isempty(&data_info.ser_to_tun_list))
        {
            data_info_lock();
            data_packet = slist_first_entry(&data_info.ser_to_tun_list, struct data_packet_s, list);
            slist_remove(&data_info.ser_to_tun_list, &data_packet->list);
            data_info_unlock();
            write(data_info.tun_fd, data_packet->data_buf, data_packet->data_len);
            tcdrain(data_info.tun_fd);
            free(data_packet);
        }
    }
    exit(EXIT_FAILURE);
}

static void sig_crash_handle(int signo)
{
    struct data_packet_s *data_packet;
    fprintf(stderr, "Caught signal %d\n", signo);
    if (data_info.tun_fd > 0) {
        close(data_info.tun_fd);
    }
    if (data_info.serial_fd > 0) {
        close(data_info.serial_fd);
    }
    while (slist_isempty(&data_info.ser_to_tun_list) == 0) {
        data_packet = slist_first_entry(&data_info.ser_to_tun_list, struct data_packet_s, list);
        slist_remove(&data_info.ser_to_tun_list, &data_packet->list);
        free(data_packet);
    }
    while (slist_isempty(&data_info.tun_to_ser_list) == 0) {
        data_packet = slist_first_entry(&data_info.tun_to_ser_list, struct data_packet_s, list);
        slist_remove(&data_info.tun_to_ser_list, &data_packet->list);
        free(data_packet);
    }
    pthread_mutex_destroy(&data_info.lock);
    exit(EXIT_FAILURE);
}

int main() {
    char tun_name[IFNAMSIZ] = "tun0";
    struct data_packet_s *data_packet;
    memset(&data_info, 0, sizeof(data_info));

    data_info.tun_fd = tun_alloc(tun_name);
    if (data_info.tun_fd < 0) {
        fprintf(stderr, "Error connecting to TUN device\n");
        return 1;
    }

    printf("TUN device %s allocated\n", tun_name);

    // Configure IP address for TUN device
    char cmd[BUFFER_SIZE];
    snprintf(cmd, sizeof(cmd), "ip addr add 10.0.0.2/24 dev %s", tun_name);
    system(cmd);
    snprintf(cmd, sizeof(cmd), "ip link set dev %s up", tun_name);
    system(cmd);

    data_info.serial_fd = open_serial(SERIAL_PORT);
    if (data_info.serial_fd < 0) {
        close(data_info.tun_fd);
        return 1;
    }

    pthread_mutex_init(&data_info.lock, NULL);

    /* 捕获以下信号，记录崩溃时的调用堆栈 */
    signal(SIGSEGV, &sig_crash_handle);	    /* 访问了无效的内存地址 */
    signal(SIGILL,  &sig_crash_handle);     /* 执行了非法的机器指令 */
    signal(SIGFPE,  &sig_crash_handle);	    /* 执行了浮点运算错误 */
    signal(SIGBUS,  &sig_crash_handle);     /* 内存对齐错误或总线错误 */
    signal(SIGABRT, &sig_crash_handle);	    /* 通过调用abort函数发出的终止信号 */
    signal(SIGKILL, &sig_crash_handle);     /* 强制终止进程 */
    signal(SIGTERM, &sig_crash_handle);	    /* 请求进程正常终止 */

    pthread_create(&data_info.tun_tid, NULL, tun_thread, NULL);
    pthread_create(&data_info.ser_tx_tid, NULL, ser_tx_thread, NULL);
    pthread_create(&data_info.ser_rx_tid, NULL, ser_rx_thread, NULL);

    pthread_join(data_info.tun_tid, NULL);
    pthread_join(data_info.ser_tx_tid, NULL);
    pthread_join(data_info.ser_rx_tid, NULL);

    close(data_info.tun_fd);
    close(data_info.serial_fd);
    while (slist_isempty(&data_info.ser_to_tun_list) == 0) {
        data_packet = slist_first_entry(&data_info.ser_to_tun_list, struct data_packet_s, list);
        slist_remove(&data_info.ser_to_tun_list, &data_packet->list);
        free(data_packet);
    }
    while (slist_isempty(&data_info.tun_to_ser_list) == 0) {
        data_packet = slist_first_entry(&data_info.tun_to_ser_list, struct data_packet_s, list);
        slist_remove(&data_info.tun_to_ser_list, &data_packet->list);
        free(data_packet);
    }
    pthread_mutex_destroy(&data_info.lock);
    return 0;
}
